/*
 * display.c
 *
 * Display routine framework.
 *
 * Based on Nigel Batten's TellyMate notes and code available at
 *  http://www.batsocks.co.uk/readme/art_SerialVideo_1.htm
 *
 * Copyright (c) 2010, Alan Green.
 * Contactable at <firstname>.<lastname>@gmail.com
 *
 * Copyright (c) 2009, Nigel Batten.
 * Contactable at <firstname>.<lastname>@mail.com
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 	1.	Redistributions of source code must retain the above copyright
 * 		notice, this list of conditions and the following disclaimer.
 * 	2.	Redistributions in binary form must reproduce the above copyright
 * 		notice, this list of conditions and the following disclaimer in the
 * 		documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * ===============================================
 */

/*
 * Circuitry:
 *
 * PB1 (OC1A, D9) -> Diode -> 1k Ohm resistor -> RCA plug tip
 * PB3 (MOSI, D11) -> Diode -> 330 Ohm resistor -> RCA plug tip
 * RCA plug outer -> GND
 *
 */
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <util/delay.h>

#include "delay_x.h"
#include "display.h"
#include "test.h"

// Buffer for display ram - one byte before and after for slop in algorithms
uint8_t display_buffer[DISPLAY_RAM_LEN + 2];

// Pointers to functions to call during display_loop
frame_function frame_logic;
line_function line_logic;

//
// sync counter, in IO memory for speed. Used to hold state for TIMER1
// interrupt, and to indicate when text safe area has started.
//
// Occupies GPIOR1 and R2
#define g_sync_count ((uint16_t *) (0x2A + __SFR_OFFSET))

// Set up program
void inline init() {
    // wdt should be off, but let's make sure
    wdt_disable();

    // Set up to sleep in idle mode
    sei();
    set_sleep_mode(SLEEP_MODE_IDLE);
    sleep_enable(); // only needs doing once (against advice).

    // Port B - PB0-PB5 correspond to D9-D13 Arduino outputs
    // Must set certain pins as output to enable SPI to work correctly
    DDRB = DDRB_SIGNAL_DISABLE;
    // All set low, except for aux pin, which is always HIGH
    PORTB = _BV(AUX_SIG_PIXEL_PIN);

    // Port C - all inputs, all pulled up
    DDRC = 0x00;
    PORTC = 0xff;

    // Port D - All inputs, all pulled up
    DDRD = 0x00;
    PORTD = 0xff;

    //
    // Timer 1 - 16 bit timer.
    //
    // WGM settings = FastPWM with ICR1=TOP.
    // COM1 settings = Inverting PWM on OC1A
    // CS settings = CLKi/o with no prescaling
    //
    //         ___________________      _ 1
    //   |____|                   |     _SYNC_PIN) | _BV(SIG_PIXEL_PIN) | (1 <
    //   ^    ^                           0
    //Bottom OC1A                ICR1
    // Sets up as if we were at g_sync_count = 285, just about to begin 286, the
    // beginning of the vertical sync sequence.
    // ICR1 - set to desired TOP value (not TOP value + 1)
    ICR1 = CYCLES_PER_SCANLINE - 1;
    // OCR1A - set for next line
    OCR1A = CYCLES_HSYNC - 1;
    // Initially, set interrupt on overflow
    TIMSK1 = _BV(TOIE1);
    TCNT1 = 0;
    (*g_sync_count) = 230; // Start at end of text-safe area

    // Compare register to cause wake from sleep during horizontal display start.
    OCR1B = CYCLES_TEXT_START;

    // Set up SPI
    SPCR = _BV(SPE) | _BV(MSTR) | _BV(CPHA);
    SPSR = _BV(SPI2X);

    // Init done - start timer
    TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(WGM11) | (0 << WGM10);
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);

    // Set up ADC converter
    // - Use Vcc as reference voltage
    // - enable ADC and start a conversion to initialize it
    // - select div by 128 for ADC clock
    // - turn off pin 5 for digital input
    ADMUX = _BV(REFS0);
    ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
    DIDR0 = _BV(ADC5D);
}

// Interrupt for timer1 overflow.
//
// Ensure correct SYNC pulse start-times and lengths.
//
// Interrupt active during non-text-safe period 230 <= g_sync_count <= 321
// Interrupt is turned on by display_loop() at end of text-safe period and
// off by this routine, at the start of the text-safe period.
//
// See http://code.google.com/p/avr-tv-game/wiki/MainLoop
//
ISR( TIMER1_OVF_vect )
{
    // Increment g_sync_count
    uint16_t count = *g_sync_count;
    count += 1;

    // Manipulate sync pulse start (ICR1) and length (OCR1A)
    // OCR1A takes effect when counter rolls over, while ICR1 takes effect
    // immediately. This means OCR1A changes have to be done the line before
    // they're needed.
    if (count == 259) {
        // Pre-equalisation short-sync
        OCR1A = CYCLES_SHORTSYNC - 1;
    } else if (count == 260) {
        // Pre-equalisation short-sync
        ICR1 = CYCLES_PER_HALF_SCANLINE - 1;
    } else if (count == 265) {
        // Broad-sync
        OCR1A = CYCLES_BROADSYNC - 1;
    } else if (count == 270) {
        // Post-equalisation short-sync
        OCR1A = CYCLES_SHORTSYNC - 1;
    } else if (count == 275) {
        // Regular sync
        OCR1A = CYCLES_HSYNC - 1;
    } else if (count == 276) {
        // Regular sync
        ICR1 = CYCLES_PER_SCANLINE - 1;
    } else if (count == 322) {
        // disable overflow interrupt - next count is 0
        TIMSK1 = (0 << TOIE1);
        count = 0;
    }
    *g_sync_count = count;
}

//
// Creates a display signal
//

static inline void output_bits(void) {
    // Output the bits in one display line, 9 bits at a time. 8 of the 9 bits
    // are output via the SPI. For the ninth bit, the SPI is in idle mode, so
    // its value is generated by switching PB0 to output if the 9th bit is a 1.

    // Each group of 9 bits are output in a "phase". Phases are 18 cpu cycles
    // long.

    // At the commencement of each phase:
    // - %[out_reg] contains the next 8 bits to be output
    // - The top bits of r0 contain data already loaded to be output next phase
    // - mul_reg is set to correctly shift next loaded value

    // Each phase consists of
    // 1. SPI idle period - disable SPI output, load new output value and
    //    re-enable the SPI output.
    // 2. Calculate 9th bit and set the bit for OC2B in DDRD appropriately.
    // 3. Calculate the value of %[out_reg] for next phase
    // 4. Burn any remaining CPU cycles.

    // There are 8 kinds of phase, characterised by the number of valid bits
    // at the top of r0, at the start of the phase.
    // A: 8
    // B: 7
    // C: 6
    // D: 5
    // E: 4
    // F: 3
    // G: 2
    // H: 1

    // There are 320 bits to output, which takes 36 phases. The last phase is a
    // short one.

    // The sequence required to output 320 pixels is
    // (a b c d e f g h) * 4, then (a b c), and a special "d" that is only 8
    // cycles long
    //
    // The total length of this output code is 10 + 18 * 36 = 658 cycles

    // Modified registers
    register uint8_t out_reg = 0; // From where next 8 bit value is stored
    register uint8_t d_count = 5; // Execute section D 5 times
    register uint8_t mul_reg = 0x02; // How much to shift by

    // Constant value
    register uint8_t spi_enable = DDRB_SIGNAL_ENABLE;

    // Will have low bit or off for aux (bit 9) enable
    register uint8_t spi_disable = DDRB_SIGNAL_DISABLE;

    // Ensure byte beyond end of display is zeroed out
    display_ram[DISPLAY_RAM_LEN] = 0x00;

    // Output bit 9 (implicit in spi_disable), then next 8 bits from out_reg
#define OUTPUT_BITS \
        "    out   %[_DDRB], %[spi_disable]    ;  0 1c disable SPI while idle           \n" \
        "    out   %[_SPDR], %[out_reg]        ;  1 1c send data                        \n" \
        "    out   %[_DDRB], %[spi_enable]     ;  2 1c re-enable SPI                    \n"

    // Output bits, then calculate bit 9 for next time
#define OUTPUT_COMMON \
    OUTPUT_BITS\
        "    cbr   %[spi_disable], %[aux]      ;  3 1c disable bit 9                    \n" \
        "    lsl   r0                          ;  4 1c but depending on top bit of r0,  \n" \
        "    brcc  9f                          ;  5 1/2c maybe...                       \n" \
        "    sbr   %[spi_disable], %[aux]      ;  6 1/0c turn it back on again          \n" \
        "9: \n"

    // Shifts part of second byte up, loads a new byte, leave out_reg and r0 ready for
    // next phase to begin
#define OUTPUT_ABCDEFG \
        "    mov   %[out_reg], r0              ;  7 1c take top bits of current byte    \n" \
        "    ld    r0, X+                      ;  8 2c get next byte                    \n" \
        "    mul   r0, %[mul_reg]              ; 10 2c shift it up                      \n" \
        "    or    %[out_reg], r1              ; 12 1c complete output value            \n" \
        "    lsl   %[mul_reg]                  ; 13 1c make mul_reg bigger              \n" \

#define NOPS_4 \
        "    rjmp  .+0                         ; 14 2c                                  \n" \
        "    rjmp  .+0                         ; 16 2c                                  \n"

    asm volatile ("\n"
            // Prologue
            "TCNT2 = 0xb2                          ; TCNT2 address                          \n"
            "    ldi   r16, 0x0b                   ; synchronize timer 2                    \n"
            "    sts   TCNT2, r16                  ; 2c ... to SPI idle period              \n"
            "    ld    %[out_reg], X+              ; get first piece of data                \n"
            "    ld    r0, X+                      ; get second piece of data               \n"

            // OUTPUT all that
            "1: \n"
            " ; A\n" OUTPUT_COMMON OUTPUT_ABCDEFG NOPS_4
            " ; B\n" OUTPUT_COMMON OUTPUT_ABCDEFG NOPS_4
            " ; C\n" OUTPUT_COMMON OUTPUT_ABCDEFG
            "    nop                               ; 14 1c                                  \n"
            "    dec   %[d_count]                  ; 15 1c Check if last iter               \n"
            "    brne  2f                          ; 16 2c/1c Continue                      \n"
            "    clr   r1                          ; 17 1c Clear R1 because loop ends       \n"
            " ; special D\n" OUTPUT_BITS
            "    cbr   %[spi_disable], %[aux]      ;  3 1c disable SPI and AUX_SIGNAL       \n"
            "    nop                               ;  4 1c nop                              \n"
            "    rjmp  .+0                         ;  5 2c nop                              \n"
            "    rjmp  .+0                         ;  7 2c nop                              \n"
            "    rjmp  .+0                         ;  9 2c nop                              \n"
            "    out   %[_DDRB], %[spi_disable]    ;  turn off output                       \n"
            "    rjmp  3f                          ;  and finish                            \n"
            "2:\n"
            " ; D\n" OUTPUT_COMMON OUTPUT_ABCDEFG NOPS_4
            " ; E\n" OUTPUT_COMMON OUTPUT_ABCDEFG NOPS_4
            " ; F\n" OUTPUT_COMMON OUTPUT_ABCDEFG NOPS_4
            " ; G\n" OUTPUT_COMMON OUTPUT_ABCDEFG NOPS_4

            " ; H\n" OUTPUT_COMMON
            "    ld   %[out_reg], X+               ;  7 2c get one byte                     \n"
            "    ld   r0, X+                       ;  9 2c get a second byte                \n"
            "    ldi  %[mul_reg], 0x02             ; 11 1c set mul_reg for going again      \n"
            "    rjmp .+0                          ; 12 2c                                  \n"
            "    rjmp .+0                          ; 14 2c                                  \n"
            "    rjmp  1b                          ; 16 2c back top                         \n"

            // Finish
            "3:\n"

            : // Outputs and modified values
            [d_count] "+&r" (d_count),
            [out_reg] "+&r" (out_reg),
            [spi_disable] "+%d" (spi_disable),
            [mul_reg] "+&r" (mul_reg)
            : // Inputs
            "x" (display_ram),
            [spi_enable] "r" (spi_enable),
            [_DDRB] "I" (_SFR_IO_ADDR(DDRB)),
            [_SPDR] "I" (_SFR_IO_ADDR(SPDR)),
            [_PB3] "M" (PB3),
            [aux] "M" (_BV(AUX_SIG_PIXEL_PIN))
            : // Clobbers

            // So, it turns out that saying "clobbers r1" is totally ignored
            // by the compiler
            // "r1", // used in multiplications
            "r16" // For loading a constant
    );
}

// At start of display line, waits for the display period to start.
static void inline wait_text_safe_start(void) {

    asm("\n"
            // Cycle counts are for shortest-path through code.
            // Shortest path is 11 cycles

            // Set up Z so that when TCNT1L is added to the low byte,
            // it magically jumps to the correct point in the NOP slide
            "    ldi  r30, lo8(pm(2f - ((%[TS] + 4)* 2)))  \n" // 1c
            "    ldi  r31, hi8(pm(2f))  \n" // 1c
            // Wait for output compare flag B (OCF1B) to be set. This is
            // a 3 cycle loop, which means the timing of the exit may
            // jitter over a 3 cycle range.
            "1:\n"
            "    sbis %[_TIFR1], 2      \n" // 2c OCF1B in TIFR1
            "    rjmp 1b                \n" // 0c
            // Work out how many nops we need to synchronize start, and execute them
            "    lds  r0, 0x0084        \n" // 2c - TCNT1L
            "    add  r30, r0           \n" // 1c
            "    ijmp                   \n" // 2c
            "2:\n" // 3-cycle nop slide
            "    nop                    \n"
            "    nop                    \n"
            "    sbi  %[_TIFR1], 2      \n" // 2c reset OCF1B in TIFR1
            : // output values
            : // input values
            [_TIFR1] "I" (_SFR_IO_ADDR(TIFR1)),
            [TS] "M" (CYCLES_TEXT_START)
            : // clobber list
            "r30","r31"
    );
}

// alignment esnures nop slide in wait_display_period_start does not
// cross page boundaries
void __attribute__((aligned(256))) display_loop() {
    // This loop executed once per frame (i.e 50 times/s)
    while (1) {

        // Program logic here - starting at g_sync_count 231
        frame_logic();

        // Set up for g_sync_count = 0
        register uint8_t sync_count = 0;
        line_logic(0);

        // wait for display period start
        while (*((volatile uint16_t *) g_sync_count) != 0) {
            // This is a 5-cycle loop
        }
        // Ensure overflow flag cleared
        TIFR1 = _BV(OCF1B);

        while (1) {
            // 11c
            wait_text_safe_start();

            // display stuff - 658c
            output_bits();

            // 16c for compare and increment g_sync_count
            if (sync_count == LAST_TEXT_SAFE_LINE) {
                break;
            } else {
                // counter incremented before start of line it is counting
                sync_count++;

                // setup for next line - 339c max (including 4c for ret!)
                line_logic(sync_count);
            }
        }

        // Renable interrupts for proper handling of frame sync
        *g_sync_count = sync_count;
        TIMSK1 = _BV(TOIE1);
    }
}

